#ifndef BSLIB_SQL_QUERY_H_
#define BSLIB_SQL_QUERY_H_

#include "sql_param.h"

class ADODB;
class SQLQuery
{
public:
	enum Type
	{
		DYNAMIC,
		PROCEDURE,
	};

	virtual ~SQLQuery();
	virtual bool Execute(ADODB* adodb) = 0;

	void BindParam(SQLParam* param);

	Type type() const { return type_; }
	typedef std::vector<SQLParam*> VECTOR_PARAMS;
	const VECTOR_PARAMS& params() const { return params_; }
	
protected:
	explicit SQLQuery(Type type);

	Type type_;
	VECTOR_PARAMS params_;
};

class SQLDynamicQuery : public SQLQuery
{
public:
	SQLDynamicQuery();
	virtual ~SQLDynamicQuery();

	virtual bool Execute(ADODB* adodb) override;

	void BindParam(const wchar_t* name, SQLParam* param);

	void AddStmt(std::wstring stmt);
	void AddParamsDef(std::wstring params_def);

	const std::wstring& stmt() const { return stmt_; }
	void set_stmt(std::wstring stmt) { stmt_ = stmt; }

	const std::wstring& params_def() const { return params_def_; }
	void set_params_def(std::wstring params_def) { params_def_ = params_def; }

protected:
	std::wstring stmt_;
	std::wstring params_def_;
};

class SQLProcedureQuery : public SQLQuery
{
public:
	explicit SQLProcedureQuery(const wchar_t* name);
	virtual ~SQLProcedureQuery();

	void BindVariableParam(SQLVariableParam* variable_param);

	virtual bool Execute(ADODB* adodb) override;

	const std::wstring& name() const { return name_; }
	
protected:
	std::wstring name_;
};

template<typename Recordset, std::size_t recordset_size = sizeof(Recordset)>
class SQLProcedureQueryWithRecordset : public SQLProcedureQuery
{
public:
	explicit SQLProcedureQueryWithRecordset(const wchar_t* name) : SQLProcedureQuery(name) {}
	virtual ~SQLProcedureQueryWithRecordset() { recordsets_.clear(); }

	void Reserve(std::size_t size)
	{
		recordsets_.reserve(size);	
	}

	virtual bool Execute(ADODB* adodb) override
	{
		if (!SQLProcedureQuery::Execute(adodb)) {
			return false;
		}

		recordsets_.clear();
		Recordset recordset;
		void* buffer = &recordset;
		short column_count = 0;
		ADODB::DataType column_type;
		std::size_t column_size = 0;
		std::size_t buffer_size = 0;
		std::size_t offset = 0;
		while (!adodb->IsRowEnd()) {
			offset = 0;
			ZeroMemory(buffer, recordset_size);
			if (!adodb->GetColumnCount(column_count)) {
				return false;
			}
			for (short column = 0; column < column_count; ++column) {
				if (!adodb->GetColumnType(column, column_type)) {
					return false;
				} else if (!adodb->GetColumnDefinedSize(column, column_size)) {
					return false;
				}
				switch (column_type) {
				case ADODB::DataWChar:
				case ADODB::DataVarWChar:
					buffer_size = column_size * sizeof(wchar_t);
					break;
				default:
					buffer_size = column_size;
					break;
				}
				if (offset + buffer_size > recordset_size) {
					return false;
				} else if (!adodb->GetColumnData(column, column_type, static_cast<unsigned char*>(buffer) + offset, buffer_size)) {
					return false;
				}
				offset += buffer_size;
			}
			if (adodb->MoveNextRow() == false) {
				return false;
			}
			recordsets_.push_back(recordset);
		}

		return true;
	}

	typedef std::vector<Recordset> VECTOR_RECORDSETS;
	const VECTOR_RECORDSETS& recordsets() const { return recordsets_; }

protected:
	VECTOR_RECORDSETS recordsets_;
};

#define DECLARE_SQL_QUERY_BEGIN(Name, Query)									\
class Name : public SQL##Query													\
	{										

#define DECLARE_SQL_QUERY_PARAM(Name, Type)										\
	private:																	\
		SQLParam Name##Param_;													\
		Type Name##_;															\
	public:																		\
		Type Get##Name() const { return Name##_; }								\
		void Set##Name(Type Name) { Name##_ = Name; }

#define DECLARE_SQL_QUERY_PARAM_STRING_A(Name, Size)							\
	private:																	\
		SQLParam Name##Param_;													\
		char Name##_[Size];														\
	public:																		\
		const char* Get##Name() const { return Name##_; }						\
		bool Set##Name(const char* Name)										\
		{																		\
			if (FAILED(StringCchCopyA(Name##_, Size, Name))) {					\
				return false;													\
			}																	\
			return true;														\
		}

#define DECLARE_SQL_QUERY_PARAM_STRING_W(Name, Size)							\
	private:																	\
		SQLParam Name##Param_;													\
		wchar_t Name##_[Size];													\
	public:																		\
		const wchar_t* Get##Name() const { return Name##_; }					\
		bool Set##Name(const wchar_t* Name)										\
		{																		\
			if (FAILED(StringCchCopyW(Name##_, Size, Name))) {					\
				return false;													\
			}																	\
			return true;														\
		}

#ifdef UNICODE
#define DECLARE_SQL_QUERY_PARAM_STRING(Name, Size) DECLARE_SQL_QUERY_PARAM_STRING_W(Name, Size)
#else // UNICODE
#define DECLARE_SQL_QUERY_PARAM_STRING(Name, Size) DECLARE_SQL_QUERY_PARAM_STRING_A(Name, Size)
#endif // UNICODE

#define DECLARE_SQL_QUERY_VARIABLE_PARAM(Name)									\
	private:																	\
		SQLVariableParam Name##Param_;											\
	public:																		\
		std::wostream& Name;													\
		void Reset##Name()														\
		{																		\
			Name##Param_.Reset();												\
		}																		\
		bool Is##Name##Empty() const											\
		{																		\
			return Name##Param_.IsEmpty();										\
		}

#define DECLARE_SQL_QUERY_CONSTRUCT(Name)										\
	public:																		\
		Name()

#define DECLARE_SQL_QUERY_CONSTRUCT_INIT_PROC(Name)								\
			: SQLProcedureQuery(L#Name)

#define DECLARE_SQL_QUERY_CONSTRUCT_INIT_RETURN_PROC(Name, Return)				\
			: SQLProcedureQuery(L#Name, Return##_)

#define DECLARE_SQL_QUERY_CONSTRUCT_INIT_PROC_WITH_RECORDSET(Name)				\
			: SQLProcedureQueryWithRecordset(L#Name)

#define DECLARE_SQL_QUERY_CONSTRUCT_INIT_PARAM_BEGIN(Name, Direction, Type)		\
			: Name##Param_(Direction, Type, &Name##_,							\
			(Type == ADODB::DataWChar) || (Type == ADODB::DataVarWChar)			\
			? sizeof(Name##_) / sizeof(wchar_t) : sizeof(Name##_))

#define DECLARE_SQL_QUERY_CONSTRUCT_INIT_PARAM(Name, Direction, Type)			\
			,Name##Param_(Direction, Type, &Name##_,							\
			(Type == ADODB::DataWChar) || (Type == ADODB::DataVarWChar)			\
			? sizeof(Name##_) / sizeof(wchar_t) : sizeof(Name##_))

#define DECLARE_SQL_QUERY_CONSTRUCT_INIT_VARIABLE_PARAM_BEGIN(Name, Size)		\
			: Name##Param_(Size), Name(Name##Param_)

#define DECLARE_SQL_QUERY_CONSTRUCT_INIT_VARIABLE_PARAM(Name, Size)				\
			,Name##Param_(Size), Name(Name##Param_)

#define DECLARE_SQL_QUERY_CONSTRUCT_INIT_END									\
		{
		
#define DECLARE_SQL_QUERY_CONSTRUCT_SET_STMT(Stmt)								\
			set_stmt(L#Stmt);

#define DECLARE_SQL_QUERY_CONSTRUCT_BIND_PARAM(Name)							\
			BindParam(&##Name##Param_);											\
			ZeroMemory(&##Name##_, sizeof(Name##_));

#define DECLARE_SQL_QUERY_CONSTRUCT_BIND_PARAM_NAME(Name)						\
			BindParam(L#Name, &##Name##Param_);									\
			ZeroMemory(&##Name##_, sizeof(Name##_));

#define DECLARE_SQL_QUERY_CONSTRUCT_BIND_VARIABLE_PARAM(Name)					\
			BindVariableParam(&##Name##Param_);

#define DECLARE_SQL_QUERY_END													\
		}																		\
	};

#endif // BSLIB_SQL_QUERY_H_